module dungeon.attack;

import std.array;

import derelict.opengl.gl;

import nade.ast;
import nade.loader;
import nade.time;
import nade.vector;
import nade.angle;
import nade.sprite;
import nade.sound;
import nade.random;

import dungeon.game;
import dungeon.map;
import dungeon.dice;
import dungeon.formula;
import dungeon.character;
import dungeon.weapon;


class Attack {
	protected {
		Game			_game;
		string			_id, _name;

		Attack			_parent;
		Attack[string]	_children;

		Sprite			_sprite;
		Sample[]		_samples;
		real			_speed = 1;
		real			_range;
		real			_delayMin = 1, _delayMax = 1;
		DiceRoll		_dice;
		Formula			_formula;
	}

	this(Game game)
	{
		_game = game;
		_parent = null;
	}
	this(Attack parent)
	{
		_game = parent._game;
		_parent = parent;

		_sprite = parent._sprite;
		_speed = parent._speed;
		_samples = parent._samples;
		_range = parent._range;
		_delayMin = parent._delayMin;
		_delayMax = parent._delayMax;
		_dice = parent._dice;
		_formula = parent._formula;
	}

	void addChild(Attack child)
	{
		_children[child.id] = child;
	}
	Attack getChild(string id)
	{
		auto child = id in _children;
		if(child is null) assert(false, id);
		return *child;
	}
	Attack getChild(AST ast, Object[string] state)
	{
		if(ast.length >= 2  &&  ast[0].length == 0  &&  ast[1].length == 0)
		{
			return getChild(ast[0].as!string).getChild(ast[1..ast.length], state);
		}
		else if(ast.length >= 1  &&  ast[0].length == 0)
		{
			auto child = getChild(ast[0].as!string);
			load(child, ast[1..ast.length], state);
			return child;
		}

		auto child = new Attack(this);
		load(child, ast, state);
		return child;
	}

	real damage(Character attacker)
	{
		real result = 0;
		if(_dice !is null) result += _dice.roll;
		if(_formula !is null) result += _formula.evaluate(attacker.stats);
		return result;
	}

	string id() { return _id; }
	string name() { return _name; }
	Sprite sprite() { return _sprite; }
	real speed() { return _speed; }
	real range() { return _range; }
	real delayMin() { return _delayMin; }
	real delayMax() { return _delayMax; }
	DiceRoll dice() { return _dice; }
	Formula formula() { return _formula; }

	static void install()
	{
		static Attack current = null;

		Loader!(Attack).add("name",
			(ref Attack attack, AST ast, Object[string] state)
			{
				attack._id = ast[0].as!string;
				attack._name = ast[1].as!string;
				if(attack._parent is null)
					attack._game.addAttack(attack);
				else
					attack._parent.addChild(attack);
			}
		);

		Loader!(Attack).add("attack",
			(ref Attack attack, AST ast, Object[string] state) { attack.getChild(ast, state); }
		);

		Loader!(Attack).add("sprite",
			(ref Attack attack, AST ast, Object[string] state) { attack._sprite = new Sprite("Textures\\Sprites\\Attacks\\" ~ ast[0].as!string ~ ".bmp"); }
		);
		Loader!(Attack).add("sample",
			(ref Attack attack, AST ast, Object[string] state)
			{
				attack._samples.length = 0;
				foreach(node; ast)
				{
					attack._samples ~= new Sample("Sounds\\" ~ node.as!string ~ ".wav");
				}
			}
		);
		
		Loader!(Attack).add("speed",
			(ref Attack attack, AST ast, Object[string] state) { attack._speed = ast[0].as!real; }
		);
		Loader!(Attack).add("range",
			(ref Attack attack, AST ast, Object[string] state) { attack._range = ast[0].as!real; }
		);
		Loader!(Attack).add("delay",
			(ref Attack attack, AST ast, Object[string] state)
			{
				attack._delayMin = attack._delayMax = ast[0].as!real;
				if(ast.length >= 2) attack._delayMax = ast[1].as!real;
			}
		);
		Loader!(Attack).add("dice",
			(ref Attack attack, AST ast, Object[string] state)
			{
				attack._dice = new DiceRoll;
				load(attack._dice, ast, state);
			}
		);
		Loader!(Attack).add("formula",
			(ref Attack attack, AST ast, Object[string] state)
			{
				attack._formula = new Formula(ast);
			}
		);
	}
}


class AttackObject : IIterator, IEffect {
	protected {
		Attack				_attack;
		CharacterObject		_attacker;
		CharacterObject		_target;
		Vector2				_position;
	}

	this(Map map, Attack attack, CharacterObject attacker, CharacterObject target)
	{
		target.map.addIterator(this);

		_attack = attack;
		_attacker = attacker;
		_attacker.addEffect(this);

		_target = target;
		_target.addEffect(this);

		_position = _attacker.position + (_target.position - _attacker.position).normal(0.4);
	}

	void render()
	{
		glPushMatrix();
		_position.translate;
		Degrees.atan2((_target.position - _attacker.position)*Vector2(-1, 1)).rotate;
		_attack._sprite.render(Vector2(1, 1));
		glPopMatrix();
	}

	void iterate()
	{
		const speed = _attack.speed*frameTime;
		const delta = _target.position - _position;
		const distance = delta.modulus - speed;
		if(distance <= 0.1)
		{
			if(!_attack._samples.empty)
			{
				auto source = new Source;
				source.position = Vector3(0, 0, 0);
				source.velocity = Vector3(0, 0, 0);
				source.play(_attack._samples[random(0, $)]);
			}

			_attacker.attack(_target, _attack);
			remove;
		}
		else
		{
			_position += delta.normal(speed);
		}
	}

	void remove()
	{
		_target.map.removeIterator(this);
		_attacker.removeEffect(this);
		_target.removeEffect(this);
	}
}
